/** @file tex_bitmap.cpp
    @brief Bitmap operations */

#include "tex/tex_bitmap.h"

using namespace G3;

//==============================================================================
// Constructors & Destructors
//==============================================================================
G3::BITMAP::BITMAP () {
	Width = 0;
	Height = 0;

	Size = 0;

	BitsPerPixel = 0;
	PackedAlignment = 0;

	NumMipMaps = 1;

	Compression = 0;

	Data = NULL;

	Log = &G3::MainLog;

	Flags = 0;
}

G3::BITMAP::~BITMAP () {
	Destroy ();
}

//==============================================================================
// = operators
//==============================================================================
G3::BITMAP& G3::BITMAP::operator=(const G3::BITMAP &b) {
	Width = b.Width;
	Height = b.Height;
	Size = b.Size;
	BitsPerPixel = b.BitsPerPixel;
	PackedAlignment = b.PackedAlignment;
	Compression = b.Compression;
	NumMipMaps = b.NumMipMaps;
	Flags = b.Flags;
	Log = b.Log;

    try {
        //! \todo Data cloning with packed Alignment support
        try {
            delete [] Data;
        } catch (...) {
            throw G3::EX ("Failed to free data", FILE_LINE);
        }

        try {
            Data = new byte [Size];
        } catch (...) {
            throw G3::EX ("Not enough memory for bitmap data", FILE_LINE);
        }

        memcpy (Data, b.Data, Size);
    } catch (G3::EX ex) {
        if (Log)
            Log->Report ("%s\n", ex.toString ().c_str ());
    }

	return *this;
}

//==============================================================================
// Allocates memory for a bitmap
//
//		int Width			Width of the bitmap (in pixels)
//
//		int Height			Height of the bitmap (in pixels)
//
//		int BitsPerPixel	Size of a pixel in bits (color depth)
//
//	Returns true on success, otherwise false
//==============================================================================
bool G3::BITMAP::Create (int Width, int Height, int ColorDepth) {
	this->Width = G3_MAX (0, G3_ABS (Width));
	this->Height = G3_MAX (0, G3_ABS (Height));

	BitsPerPixel = G3_MAX (1, G3_MIN (32, G3_ABS (ColorDepth)));

	Size = this->Width * this->Height * BitsPerPixel / 8;

    try {
        Data = new byte [Size];
    } catch (...) {
		if (Log)
			Log->Report ("ERROR! BITMAP::Create: Not enough memory for creating a bitmap (at least %d more bytes needed)..\n", Width * Height * BitsPerPixel / 8);
		return false;
    }

	return true;
}

//==============================================================================
// Sets a pixel (not a very fast method)
//
//		uint x				Pixel x-coordinate
//
//		uint y				Pixel y-coordinate
//
//		COL4B				Color of the pixel
//==============================================================================
void G3::BITMAP::SetPixel (uint x, uint y, COL4B col) {
	Assert (Data, "BITMAP::SetPixel: Data member is a NULL pointer");
	Assert (x < Width, "BITMAP::SetPixel: x argument is invalid (>= Width)");
	Assert (y < Height, "BITMAP::SetPixel: y argument is invalid (>= Height)");

	long b = 0;
	int bypp = BitsPerPixel / 8;

	if ((Flags & BITMAP_GRAYSCALE) != 0) {
		Data [Width * y + x] = (byte) ((col.r + col.g + col.b + col.a) / 4);
	} else if ((Flags & BITMAP_RGBA) != 0) {
		Data [bypp * (Width * y + x)    ] = col.r;
		Data [bypp * (Width * y + x) + 1] = col.g;
		Data [bypp * (Width * y + x) + 2] = col.b;
		Data [bypp * (Width * y + x) + 3] = col.a;
	} else if (BitsPerPixel == 24) {
		Data [bypp * (Width * y + x)    ] = col.r;
		Data [bypp * (Width * y + x) + 1] = col.g;
		Data [bypp * (Width * y + x) + 2] = col.b;
	} else if (BitsPerPixel == 16) {
	    b = (((uint) (col.r * 31.0f)) | (((uint) (col.g * 63.0f)) << 5) | (((uint) (col.b * 31.0f)) << 11));

		memcpy (Data + bypp * (y * Width + x), &b, bypp);
    }
}

//==============================================================================
// Gets a pixel (not a very fast method)
//
//		uint x				Pixel x-coordinate
//
//		uint y				Pixel y-coordinate
//
//	Returns the	color of the pixel
//==============================================================================
COL4B G3::BITMAP::GetPixel (uint x, uint y) {
	Assert (Data, "BITMAP::SetPixel: Data member is a NULL pointer");
	Assert (x < Width, "BITMAP::SetPixel: x argument is invalid (>= Width)");
	Assert (y < Height, "BITMAP::SetPixel: y argument is invalid (>= Height)");

    long b = 0;
    int bypp = BitsPerPixel / 8;
	COL4B col;

	if ((Flags & BITMAP_GRAYSCALE) != 0) {
		col.r = Data [Width * y + x];
		col.g = col.r;
		col.b = col.r;
		col.a = 255;
	} else if ((Flags & BITMAP_RGBA) != 0) {
		col.r = Data [4 * (Width * y + x)    ];
		col.g = Data [4 * (Width * y + x) + 1];
		col.b = Data [4 * (Width * y + x) + 2];
		col.a = Data [4 * (Width * y + x) + 3];
	} else if (BitsPerPixel == 24){
		col.r = Data [3 * (Width * y + x)    ];
		col.g = Data [3 * (Width * y + x) + 1];
		col.b = Data [3 * (Width * y + x) + 2];
		col.a = 255;
	} else if (BitsPerPixel == 16) {
	    COL4F col2;
	    memcpy (&b, Data + (y * Width + x) * bypp, bypp);
        col2.r = (b & 0x1F) / 31.0f;
        col2.g = ((b >> 5) & 0x3F) / 63.0f;
        col2.b = ((b >> 11) & 0x1F) / 31.0f;
        col.r = (byte) ((int) (col2.r * 255.0f));
        col.g = (byte) ((int) (col2.g * 255.0f));
        col.b = (byte) ((int) (col2.b * 255.0f));
        col.a = 255;
	}
	return col;
}

//==============================================================================
// Checks if the bitmap data is valid
//==============================================================================
bool G3::BITMAP::IsValid () {
	return (Data && Width > 0 && Height > 0 && BitsPerPixel > 0);
}

//==============================================================================
// Clears the bitmap (fills with black pixels)
//==============================================================================
void G3::BITMAP::Clear () {
	long scanline = Width * Height * BitsPerPixel;

	for (long i=0; i<scanline; i++)
		Data [i] = 0;
}

//==============================================================================
// Destroys the bitmap
//==============================================================================
void G3::BITMAP::Destroy () {
    try {
        delete [] Data;
    } catch (...) {
        if (Log)
            Log->Report ("%s: Failed to free data\n", FILE_LINE_STR.c_str ());
    }

    Data = NULL;
}

//==============================================================================
// Draws a rectangle outline onto the bitmap
//==============================================================================
bool G3::DrawRect (G3::BITMAP *bmp, uint x1, uint y1, uint x2, uint y2, COL4F col) {
	Assert (bmp, "DrawRect: Bitmap argument is a NULL pointer");

    uint x = 0, y = 0;
	COL4B col2;

	col2.r = (byte) (255.0f * col.r);
	col2.g = (byte) (255.0f * col.g);
	col2.b = (byte) (255.0f * col.b);
	col2.a = (byte) (255.0f * col.a);

    for (x = x1; x <= x2; x++) {
        bmp->SetPixel (x, y1, col2);
		bmp->SetPixel (x, y2, col2);
	}
    for (y = y1; y <= y2; y++) {
        bmp->SetPixel (x1, y, col2);
		bmp->SetPixel (x2, y, col2);
	}

    return true;
}

//==============================================================================
// Draws a filled rectangle onto the bitmap
//==============================================================================
bool G3::DrawRectFill (G3::BITMAP *bmp, uint x1, uint y1, uint x2, uint y2, COL4F col) {
	Assert (bmp, "DrawRectFill: Bitmap argument is a NULL pointer");

    uint x = 0, y = 0;
	COL4B col2;

	col2.r = (byte) (255.0f * col.r);
	col2.g = (byte) (255.0f * col.g);
	col2.b = (byte) (255.0f * col.b);
	col2.a = (byte) (255.0f * col.a);

    for (x = x1; x <= x2; x++)
        for (y = y1; y <= y2; y++)
            bmp->SetPixel (x, y, col2);

    return true;
}

//==============================================================================
// Flips the bitmap vertically
//
//	Returns true on success, false on failure
//==============================================================================
bool G3::FlipVertical (G3::BITMAP *bmp) {
	Assert (bmp, "FlipVertical: Bitmap argument is a NULL pointer");

    ulong i = 0;
    uint Bypp = bmp->BitsPerPixel / 8;
    ulong scanline = bmp->Width * Bypp;
    byte *d = NULL;

    try {
        d = new byte [scanline + 1];

        for (i=0; i < bmp->Height / 2; i++) {
            memcpy (d, bmp->Data + i * scanline, scanline);
            memcpy (bmp->Data + i * scanline, bmp->Data + (bmp->Height - 1 - i) * scanline, scanline);
            memcpy (bmp->Data + (bmp->Height - 1 - i) * scanline, d, scanline);
        }
    } catch (...) {
        if (bmp->Log)
            bmp->Log->Report ("%s: Failed to allocate memory for a scanline\n", FILE_LINE_STR.c_str ());

        return false;
    }

    try {
        delete [] d;
    } catch (...) {
        if (bmp->Log)
            bmp->Log->Report ("%s: Failed to free a scanline\n", FILE_LINE_STR.c_str ());
    }

    return true;
}

//==============================================================================
// Flips the bitmap horizontally
//
//	Returns true on success, false on failure
//==============================================================================
bool G3::FlipHorizontal (G3::BITMAP *bmp) {
	Assert (bmp, "FlipHorizontal: Bitmap argument is a NULL pointer");

    ulong i = 0, j = 0;
    uint Bypp = bmp->BitsPerPixel / 8;
    ulong scanline = bmp->Width * Bypp;
    byte b = 0;

    for (j=0; j<bmp->Height; j++) {
        for (i=0; i < scanline; i+=Bypp) {
            if (i > scanline / 2) break;
            b = bmp->Data [j * scanline + i];
            bmp->Data [j * scanline + i] = bmp->Data [j * scanline + (scanline - i)];
            bmp->Data [j * scanline + (scanline - i)] = b;
        }
    }

    return true;
}

//==============================================================================
// Blits a bitmap onto another
//
//	Returns true on success, false on failure
//
//	TODO:: Test it out
//==============================================================================
bool G3::BlitBitmap (G3::BITMAP *Src, G3::BITMAP *Dest, uint x1, uint y1, uint x2, uint y2, uint w, uint h) {
	Assert (Src, "BlitBitmap: Source argument is a NULL pointer");
	Assert (Src->Data, "BlitBitmap: Data member of the Source Bitmap is a NULL pointer");
	Assert (Dest, "BlitBitmap: Destination argument is a NULL pointer");
	Assert (Dest->Data, "BlitBitmap: Data member of the Source Bitmap is a NULL pointer");

	uint x = 0, y = 0, xi = x2, yi = y2;
	COL4B c;
	for (x = x1; x < x1 + w; x++) {
		yi = y2;
		for (y = y1; y < y1 + h; y++) {
			c = Src->GetPixel (x, y);
			Dest->SetPixel (xi, yi, c);
			yi++;
		}
		xi++;
	}
	return true;
}

//==============================================================================
// Shrinks the bitmap to 2^S
//==============================================================================
bool G3::MakePow2 (G3::BITMAP *bmp) {
    Assert (bmp, "MakePow2: Bitmap argument is a NULL pointer");

    G3::BITMAP backup = *bmp;

    bmp->Width -= bmp->Width % 2;
    bmp->Height -= bmp->Height % 2;

    try {
        bmp->Data = new byte [bmp->Width * bmp->Height * bmp->BitsPerPixel / 8 + 1];
    } catch (...) {
        if (bmp->Log)
            bmp->Log->Report ("%s: Not enough memory for temporary bitmap data\n", FILE_LINE_STR.c_str ());

        return false;
    }

    BlitBitmap (&backup, bmp, 0, 0, 0, 0, bmp->Width, bmp->Height);

    return true;
}
